/* 
   Socket.cpp

   Copyright (C) 2002-2004 Ren� Nyffenegger

   This source code is provided 'as-is', without any express or implied
   warranty. In no event will the author be held liable for any damages
   arising from the use of this software.

   Permission is granted to anyone to use this software for any purpose,
   including commercial applications, and to alter it and redistribute it
   freely, subject to the following restrictions:

   1. The origin of this source code must not be misrepresented; you must not
      claim that you wrote the original source code. If you use this source code
      in a product, an acknowledgment in the product documentation would be
      appreciated but is not required.

   2. Altered source versions must be plainly marked as such, and must not be
      misrepresented as being the original source code.

   3. This notice may not be removed or altered from any source distribution.

   Ren� Nyffenegger rene.nyffenegger@adp-gmbh.ch

   poulet24 2009 - Source modified for Popup. Bugs may have been introduced.
*/

#include "stdafx.h"
#include "Socket.h"
#include <iostream>
#include "PopupPingPong.h"

#pragma warning(disable:4996)

// In case we use non blocking socket, and we receive
// a WSAWOULDBLOCK error, we'll sleep this amount of
// milliseconds before retrying to recv/send data
#define SLEEP_TIME_BEFORE_NEXT_TRY_MS 3

using namespace std;

int Socket::nofSockets_= 0;

void Socket::Start() {
  if (!nofSockets_) {
    WSADATA info;
    if (WSAStartup(MAKEWORD(2,0), &info)) {
      throw Socket::Exception("Could not start WSA");
    }
  }
  ++nofSockets_;
}

void Socket::End() {
  WSACleanup();
}

Socket::Socket() : s_(0), m_keepalive(false) {
  Start();
  // UDP: use SOCK_DGRAM instead of SOCK_STREAM
  s_ = socket(AF_INET,SOCK_STREAM,0);

  if (s_ == INVALID_SOCKET) {
    throw Socket::Exception("INVALID_SOCKET");
  }

  refCounter_ = new int(1);
}

Socket::Socket(SOCKET s) : s_(s) {
  Start();
  refCounter_ = new int(1);
};

Socket::~Socket() {
  if (! --(*refCounter_)) {
    Close();
    delete refCounter_;
  }

  --nofSockets_;
  if (!nofSockets_) End();
}

Socket::Socket(const Socket& o) {
  refCounter_=o.refCounter_;
  (*refCounter_)++;
  s_         =o.s_;

  nofSockets_++;
}

Socket& Socket::operator=(Socket& o) {
  (*o.refCounter_)++;

  refCounter_=o.refCounter_;
  s_         =o.s_;

  nofSockets_++;

  return *this;
}

void Socket::Close() {
  closesocket(s_);
}


void Socket::resetKeepalive()
{
	m_keepaliveCountDownMs = m_keepaliveConfigTimeoutMs;
	m_keepalivePacketSent  = false;
}

bool Socket::handleKeepalive(size_t p_sleepTimeMs)
{
	bool _rc = true;

	if (m_keepalive == true)
	{
		// Update the time spent, waiting for packets
		m_keepaliveCountDownMs -= p_sleepTimeMs;

		// Deadline! Something must be wrong!
		if (m_keepaliveCountDownMs <= 0)
		{
			_rc = false;
		}
		// If we have only 2 seconds remaining before timeout
		// is reached, let's send a ping request
		else if ((m_keepaliveCountDownMs <= 2000) &&
				 (m_keepalivePacketSent == false))
		{
			// Send a ping request!
			PopupPing _ping;
			m_keepalivePacketSent = _ping.sendTo(this);
			_rc = m_keepalivePacketSent;
		}
	}

	return _rc;
}

bool Socket::ReceiveBytes(char *p_buff, size_t p_size = 0)
{
  bool  _rc = true;
  size_t _remainingBytes = p_size;
  int _nbBytesRead = 0;
  char *_ptr = p_buff;
  	  
  while (_rc == true && _remainingBytes > 0)
  {
	  _nbBytesRead = recv(s_, (char*) _ptr, _remainingBytes, 0);

	  // Success
	  if (_nbBytesRead > 0)
	  {
		resetKeepalive();
		_remainingBytes -= _nbBytesRead;
		_ptr = _ptr + _nbBytesRead;
	  }
	  // Socket gracefuly closed
	  else if (_nbBytesRead == 0) 
	  {
		 _rc = false;
	  }
	  // Socket error
	  else if (_nbBytesRead == SOCKET_ERROR)
	  {
		  int _err = WSAGetLastError();
		  if (_err == WSAEWOULDBLOCK) // Failed simply because of non-blocking status
		 {
			 // Sleep a little bit
			 Sleep(SLEEP_TIME_BEFORE_NEXT_TRY_MS);
			 // Make sure we're not disconnected
			 _rc = handleKeepalive(SLEEP_TIME_BEFORE_NEXT_TRY_MS);
		  }
	  	  else  // Fatal socket error (wire disconnected?)
		  {
			_rc = false;
		  }
	  }
	  // Unexpected
	  else
	  {
	    _rc = false;
	  }
  }

  return _rc;
}

bool Socket::SendMessage(size_t p_size, char *p_buff)
{
	size_t _remainingBytes = p_size;
	int _nbBytesSent;
	char *_ptr = p_buff;
	bool _rc = true;

	while (_rc == true && _remainingBytes > 0)
	{
		_nbBytesSent = send(s_, _ptr, _remainingBytes, 0);

		// Success
		if (_nbBytesSent > 0)
		{
			//resetKeepalive();
			_remainingBytes -= _nbBytesSent;
			_ptr += _nbBytesSent;
		}
		// Socket error
		else if (_nbBytesSent == SOCKET_ERROR)
		{
			int _err = WSAGetLastError();
			// In case socket fails because of its non-blocking
			// status, we sleep until message could be really 
			// delivered. This is because of home-made keep-alive
			// which needs to send PING requests, even though socket
			// is made busy by another task (like a big file receival)
			if (_err == WSAEWOULDBLOCK)
			{
				Sleep(SLEEP_TIME_BEFORE_NEXT_TRY_MS);
			}
			else // Fatal socket error (wire disconnected?)
			{
				_rc = false;
			}
		}
		// Unexpected
		else
		{
			_rc = false;
		}
	}

	return _rc;
}

void Socket::enableKeepAlive(bool p_doEnable, int p_timeout)
{
	// Mezmerize keepalive params
	m_keepalive = true;
	m_keepaliveConfigTimeoutMs = (p_timeout * 1000);
	resetKeepalive();
	// Switch socket to non blocking mode
	u_long arg = 1;
    ioctlsocket(s_, FIONBIO, &arg);
}

SocketServer::SocketServer(int port, int connections, TypeSocket type) {
  sockaddr_in sa;

  memset(&sa, 0, sizeof(sa));

  sa.sin_family = PF_INET;             
  sa.sin_port = htons(port);          
  s_ = socket(AF_INET, SOCK_STREAM, 0);
  if (s_ == INVALID_SOCKET) {
    throw Socket::Exception("INVALID_SOCKET");
  }

  if(type==NonBlockingSocket) {
    u_long arg = 1;
    ioctlsocket(s_, FIONBIO, &arg);
  }

  /* bind the socket to the internet address */
  if (bind(s_, (sockaddr *)&sa, sizeof(sockaddr_in)) == SOCKET_ERROR) {
    closesocket(s_);
    throw Socket::Exception("INVALID_SOCKET");
  }
  else
  {
    listen(s_, connections);                               
  }  
}

Socket* SocketServer::Accept() {
  SOCKET new_sock = accept(s_, 0, 0);
  if (new_sock == INVALID_SOCKET) {
    int rc = WSAGetLastError();
    if(rc==WSAEWOULDBLOCK) {
      return 0; // non-blocking call, no request pending
    }
    else {
      throw Socket::Exception("Invalid Socket");
    }
  }

  Socket* r = new Socket(new_sock);
  return r;
}

SocketClient::SocketClient(const std::string& host, int port) : Socket() {
  std::string error;

  hostent *he;
  if ((he = gethostbyname(host.c_str())) == 0) {
    error = strerror(errno);
    throw error;
  }

  sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_port = htons(port);
  addr.sin_addr = *((in_addr *)he->h_addr);
  memset(&(addr.sin_zero), 0, 8); 

  if (::connect(s_, (sockaddr *) &addr, sizeof(sockaddr))) {
    error = strerror(WSAGetLastError());
    throw error;
  }
}

SocketSelect::SocketSelect(Socket const * const s1, Socket const * const s2, TypeSocket type) {
  FD_ZERO(&fds_);
  FD_SET(const_cast<Socket*>(s1)->s_,&fds_);
  if(s2) {
    FD_SET(const_cast<Socket*>(s2)->s_,&fds_);
  }     

  TIMEVAL tval;
  tval.tv_sec  = 0;
  tval.tv_usec = 1;

  TIMEVAL *ptval;
  if(type==NonBlockingSocket) {
    ptval = &tval;
  }
  else { 
    ptval = 0;
  }

  if (select (0, &fds_, (fd_set*) 0, (fd_set*) 0, ptval) == SOCKET_ERROR) 
    throw Socket::Exception("Error in select");
}

bool SocketSelect::Readable(Socket const* const s) {
  if (FD_ISSET(s->s_,&fds_)) return true;
  return false;
}
